##  Cate - Copyright (C) 2006 - Marco Bubke
##  This program is free software; you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation; either version 2, or (at your option)
##  any later version.
##  
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.

from PyQt4 import QtCore, QtGui, Qsci, QtOpenGL
import shader
from OpenGL import GL, error
import math
import sys
import types
from elementtree.ElementTree import Element, SubElement



__all__ = ['OpenGLView']

class LocalNameSpaceDict(dict):
  def __init__(self, openGLView):
    dict.__init__(self)
    self.openGLView = openGLView
  def __setitem__(self, key, value):
    if key not in self:
      self.openGLView.newOpenGLObjectInLocalNameSpaceDict(key, value)
    dict.__setitem__(self, key, value)
    
  def __delitem__(self, key):
    if hasattr(value, 'name'):
      value.name = key
    self.openGLView.deletedOpenGLObjectInLocalNameSpaceDict(key)
    dict.__setitem__(self, key, value)
    
class OpenGLView(QtOpenGL.QGLWidget):
  def __init__(self, parent=None):
    
    openglWindowFormat = self.createOpenGLWindowFormat()
    
    QtOpenGL.QGLWidget.__init__(self, openglWindowFormat, parent)
    
    self.trolltechGreen = QtGui.QColor.fromCmykF(0.40, 0.0, 1.0, 0.0)
    self.trolltechPurple = QtGui.QColor.fromCmykF(0.39, 0.39, 0.0, 0.0)
        
    self.lastPos = QtCore.QPoint()
    
    self.paintCode = None
    self.resizeCode = None
    self.initCode = None
    
    self.initialized = False

  def createLocalNameSpace(self):
    self.localNameSpace = LocalNameSpaceDict(self)
    self.localNameSpace['object'] = self.makeObject()
    self.localNameSpace['xRotation'] = 0.0
    self.localNameSpace['yRotation'] = 0.0
    self.localNameSpace['zRotation'] = 0.0
    self.localNameSpace['width'] = self.width()
    self.localNameSpace['height'] = self.height()
    self.localNameSpace['createProgam'] =self.createProgram
    self.localNameSpace['createFragmentShader'] =self.createFragmentShader
    self.localNameSpace['createVertexShader'] =self.createVertexShader
    self.localNameSpace['attachShaderToProgram'] =self.attachShaderToProgram
    self.emit(QtCore.SIGNAL("localNameSpacePopulated"), 
      self.localNameSpace)
     
  def setLocalNameSpace(self, key, value):
    self.localNameSpace[key] = value

    
  def newOpenGLObjectInLocalNameSpaceDict(self, key, item):
    self.emit(QtCore.SIGNAL('newLocalNameSpaceItem'), key, item)
    
  def deletedOpenGLObjectInLocalNameSpaceDict(self, key):
    self.emit(QtCore.SIGNAL('deletedLocalNameSpaceItem'), key)
    
  def getLocalNameSpace(self, key):
    return self.localNameSpace[key] 

  def createOpenGLWindowFormat(self):
     openglWindowFormat = QtOpenGL.QGLFormat()
     openglWindowFormat.setDepthBufferSize(24)
     openglWindowFormat.setStencil(True)
     openglWindowFormat.setStencilBufferSize (8)
     openglWindowFormat.setSampleBuffers(True)
     openglWindowFormat.setAlpha(True)    
     return openglWindowFormat
    
  def initializeGL(self):
    self.createGlobalNameSpace()
    self.createLocalNameSpace()
    self.createShader()
    
    self.updateInitCode()
    
    self.emit(QtCore.SIGNAL("initialized()"))
    self.initialized = True
    
  def updateInitCode(self):
    if self.initCode:
      try:
        exec self.initCode in self.globalNameSpace, self.localNameSpace
      except (NameError, AttributeError, TypeError, error.GLError):
        self.emit(QtCore.SIGNAL("initCodeError"), sys.exc_value)
               

  def createGlobalNameSpace(self):
    self.globalNameSpace = {}
    self.globalNameSpace.update(GL.__dict__)
    self.globalNameSpace.update(shader.__dict__)
    self.emit(QtCore.SIGNAL("globalNameSpacePopulated"), 
      self.globalNameSpace)    
##    for item in self.paintCodeNameSpace.iteritems():
##      if isinstance(item[1], types.ModuleType):
##        self.paintCodeNameSpace.pop(item[0])

    
    

  def createShader(self):
    self.createProgram('program')
    self.createFragmentShader('fragmentShader')
    self.createVertexShader('vertexShader')
    self.attachShaderToProgramByName('fragmentShader', 'program')
    self.attachShaderToProgramByName('vertexShader', 'program')


  def updateResizeCode(self):
    if self.resizeCode:
      try:
        exec self.resizeCode in self.globalNameSpace, self.localNameSpace
      except (NameError, AttributeError, TypeError, error.GLError):
        self.emit(QtCore.SIGNAL("resizeCodeError"), sys.exc_value)
        
  def resizeGL(self, width, height):
    self.localNameSpace['width'] = width
    self.localNameSpace['height'] = height
    self.updateResizeCode()

  def updateGL(self):
    # this is a workaround for a endless initialization
    if self.initialized:
      QtOpenGL.QGLWidget.updateGL(self)

  def paintGL(self):
##    error.off()
    
    if self.paintCode:
      try:
        exec self.paintCode in self.globalNameSpace, self.localNameSpace
      except (NameError, AttributeError, TypeError, error.GLError):
        self.emit(QtCore.SIGNAL("paintCodeError"), sys.exc_value)

##    try:
##      error.on()
##    except error.GLError:
##      pass

  def mousePressEvent(self, event):
      self.lastPos = QtCore.QPoint(event.pos())

  def mouseMoveEvent(self, event):
      dx = event.x() - self.lastPos.x()
      dy = event.y() - self.lastPos.y()

      if event.buttons() & QtCore.Qt.LeftButton:
          self.setXRotation(self.localNameSpace['xRotation'] + 8 * dy)
          self.setYRotation(self.localNameSpace['yRotation'] + 8 * dx)
      elif event.buttons() & QtCore.Qt.RightButton:
          self.setXRotation(self.localNameSpace['xRotation'] + 8 * dy)
          self.setZRotation(self.localNameSpace['zRotation'] + 8 * dx)

      self.lastPos = QtCore.QPoint(event.pos())

  def makeObject(self):
      genList = GL.glGenLists(1)
      GL.glNewList(genList, GL.GL_COMPILE)

      GL.glBegin(GL.GL_QUADS)

      x1 = +0.06
      y1 = -0.14
      x2 = +0.14
      y2 = -0.06
      x3 = +0.08
      y3 = +0.00
      x4 = +0.30
      y4 = +0.22

      self.quad(x1, y1, x2, y2, y2, x2, y1, x1)
      self.quad(x3, y3, x4, y4, y4, x4, y3, x3)

      self.extrude(x1, y1, x2, y2)
      self.extrude(x2, y2, y2, x2)
      self.extrude(y2, x2, y1, x1)
      self.extrude(y1, x1, x1, y1)
      self.extrude(x3, y3, x4, y4)
      self.extrude(x4, y4, y4, x4)
      self.extrude(y4, x4, y3, x3)

      Pi = 3.14159265358979323846
      NumSectors = 200

      for i in range(NumSectors):
          angle1 = (i * 2 * Pi) / NumSectors
          x5 = 0.30 * math.sin(angle1)
          y5 = 0.30 * math.cos(angle1)
          x6 = 0.20 * math.sin(angle1)
          y6 = 0.20 * math.cos(angle1)

          angle2 = ((i + 1) * 2 * Pi) / NumSectors
          x7 = 0.20 * math.sin(angle2)
          y7 = 0.20 * math.cos(angle2)
          x8 = 0.30 * math.sin(angle2)
          y8 = 0.30 * math.cos(angle2)

          self.quad(x5, y5, x6, y6, x7, y7, x8, y8)

          self.extrude(x6, y6, x7, y7)
          self.extrude(x8, y8, x5, y5)

      GL.glEnd()
      GL.glEndList()

      return genList

  def quad(self, x1, y1, x2, y2, x3, y3, x4, y4):
      self.qglColor(self.trolltechGreen)

      GL.glVertex3d(x1, y1, -0.05)
      GL.glVertex3d(x2, y2, -0.05)
      GL.glVertex3d(x3, y3, -0.05)
      GL.glVertex3d(x4, y4, -0.05)

      GL.glVertex3d(x4, y4, +0.05)
      GL.glVertex3d(x3, y3, +0.05)
      GL.glVertex3d(x2, y2, +0.05)
      GL.glVertex3d(x1, y1, +0.05)

  def extrude(self, x1, y1, x2, y2):
      self.qglColor(self.trolltechGreen.dark(250 + int(100 * x1)))

      GL.glVertex3d(x1, y1, +0.05)
      GL.glVertex3d(x2, y2, +0.05)
      GL.glVertex3d(x2, y2, -0.05)
      GL.glVertex3d(x1, y1, -0.05)

  def normalizeAngle(self, angle):
      while angle < 0:
          angle += 360 * 16
      while angle > 360 * 16:
          angle -= 360 * 16
      return angle
      

  def minimumSizeHint(self):
      return QtCore.QSize(50, 50)

  def sizeHint(self):
      return QtCore.QSize(400, 400)

  def setXRotation(self, angle):
      angle = self.normalizeAngle(angle)
      if angle != self.localNameSpace['xRotation']:
          self.localNameSpace['xRotation'] = angle
          self.emit(QtCore.SIGNAL("xRotationChanged"), angle)
          self.updateGL()

  def setYRotation(self, angle):
      angle = self.normalizeAngle(angle)
      if angle != self.localNameSpace['yRotation']:
          self.localNameSpace['yRotation'] = angle
          self.emit(QtCore.SIGNAL("yRotationChanged"), angle)
          self.updateGL()

  def setZRotation(self, angle):
      angle = self.normalizeAngle(angle)
      if angle != self.localNameSpace['zRotation']:
          self.localNameSpace['zRotation'] = angle
          self.emit(QtCore.SIGNAL("zRotationChanged"), angle)
          self.updateGL()

  def changeLocalNameSpaceXMLTree(self, objects):
    for item in self.localNameSpace.iteritems():
      if hasattr(item[1], 'changeXMLByAttributes'):
        node = item[1].changeXMLByAttributes(objects)
        
  def createVertexShader(self, name, source=None):
    if not source:
      sourceFile = open('share/basic.vert', 'r')
      source = sourceFile.read()
    vertexShader = shader.VertexShader(name, source)
    self.setLocalNameSpace(name, vertexShader)
    
    
  def createFragmentShader(self, name, source=None):
    if not source:
      sourceFile = open('share/basic.frag', 'r')
      source = sourceFile.read()
    fragmentShader = shader.FragmentShader(name, source)
    self.setLocalNameSpace(name, fragmentShader)
    
  
  def createProgram(self, name):
    program = shader.Program(name)
    self.setLocalNameSpace(name, program)
    self.connect(program, QtCore.SIGNAL("linkingOk"), 
      self.updateGL)
    
  def attachShaderToProgram(self, shader, program):
    program.attach(shader)
    
  def attachShaderToProgramByName(self, shader, program):
    program = self.localNameSpace[program]
    shader = self.localNameSpace[shader]
    program.attach(shader)

  def detachShaderFromProgram(self, shader, program):
    program.detach(shader)
    
  def detachShaderFromProgramByName(self, shader, program):
    program = self.localNameSpace[program]
    shader = self.localNameSpace[shader]
    program.detach(shader)
